/*
* "PS2" Application Framework
*
* University of Abertay Dundee
* May be used for educational purposed only
*
* Author - Dr Henry S Fortuna
*
* $Revision: 1.4 $
* $Date: 2007/08/27 19:37:12 $
*
*/

#ifndef _PIPELINE_H_
#define _PIPELINE_H_

#include "boundingbox.h"
#include "boundingsphere.h"
#include "frustum.h"
#include "frustumcullingtype.h"
#include "singleton.h"
#include "ps2matrix4x4.h"
#include "ps2vector4.h"
#include "toolkit.h"

#define Pipeline CPipeline::GetSingleton()

class CPipeline : public CSingleton<CPipeline> {
public:
    CPipeline();
    ~CPipeline() {};

    inline const Matrix4x4 & GetProjection(void) const;
    inline const Matrix4x4 & GetCamera(void) const;
    inline const Matrix4x4 & GetViewProjection(void) const;
    inline const Matrix4x4 & GetLightDirs(void) const;
    inline const Matrix4x4 & GetLightCols(void) const;
    inline const Vector4 & GetScaleVector(void) const;

    inline const float GetCameraX(void) const;
    inline const float GetCameraY(void) const;
    inline const float GetCameraZ(void) const;
    inline const float GetCameraXRot(void) const;
    inline const float GetCameraYRot(void) const;

    void SetLight1(const Vector4 & Direction, const Vector4 & Colour);
    void SetLight2(const Vector4 & Direction, const Vector4 & Colour);
    void SetLight3(const Vector4 & Direction, const Vector4 & Colour);
    void SetAmbient(const Vector4 & Colour);

    void Update(float Strafe, float Advance, float UpDown, float XRot, float YRot);
    void PositionCamera(const Vector4 & Position, const float XRot, const float YRot);
    void PositionCamera(const Vector4 & Position, const Vector4 & LookAt);
    void PositionCamera(const Vector4 & Position, const Vector4 & LookAt, const Vector4 & Up);
    void Initialise(void);

    const float GetFarPlane() const;
    void SetFarPlane(const float val);
    const float GetNearPlane() const;
    void SetNearPlane(const float val);
    const float GetVisibleRadius() const;
    void SetVisibleRadius(float val);
    void UpdateViewFrustum();
    inline FrustumCullingType BoxInFrustum(const BoundingBox* boundingBox);
    inline FrustumCullingType SphereInFrustum(const BoundingSphere* boundingSphere);
    FrustumCullingType SphereInRadius(const BoundingSphere* boundingSphere, float radius);
    FrustumCullingType SphereInVisibleRadius(const BoundingSphere* boundingSphere);
protected:

    float m_near;		// Near plane
    float m_far;		// Far plane
    float m_FOV;		// Field of View
    float m_Aspect;		// Aspect Ratio
    float m_ScreenW;	// Screen Width
    float m_ScreenH;	// Screen height;

    Matrix4x4 m_Projection;
    Matrix4x4 m_FrustumProjection;
    Matrix4x4 m_Camera;
    Matrix4x4 m_ViewProjection;
    Matrix4x4 m_LightDirs;
    Matrix4x4 m_LightCols;

    Vector4 m_Scale;

    Vector4 m_CamPos;
    float m_XRot, m_YRot;

    Frustum m_ViewFrustum;
    float m_VisibleRadius;
    void CalcProjection(void);
};

inline const Matrix4x4 & CPipeline::GetProjection(void) const {
    return (m_Projection);
}

inline const Matrix4x4 & CPipeline::GetCamera(void) const {
    return (m_Camera);
}

inline const Matrix4x4 & CPipeline::GetViewProjection(void) const {
    return (m_ViewProjection);
}

inline const Matrix4x4 & CPipeline::GetLightDirs(void) const {
    return (m_LightDirs);
}

inline const Matrix4x4 & CPipeline::GetLightCols(void) const {
    return (m_LightCols);
}

inline const Vector4 & CPipeline::GetScaleVector(void) const {
    return (m_Scale);
}

inline const float CPipeline::GetCameraX(void) const {
    return m_CamPos.x;
}

inline const float CPipeline::GetCameraY(void) const {
    return m_CamPos.y;
}

inline const float CPipeline::GetCameraZ(void) const {
    return m_CamPos.z;
}

inline const float CPipeline::GetCameraXRot(void) const {
    return m_XRot;
}

inline const float CPipeline::GetCameraYRot(void) const {
    return m_YRot;
}

inline FrustumCullingType CPipeline::BoxInFrustum(const BoundingBox* boundingBox) {
    int iTotalIn = 0;

    // get the corners of the box into the vCorner array
    Vector4 vertices[8];
    Toolkit::GetVertices(boundingBox, &vertices[0]);

    // test all 8 corners against the 6 sides
    // if all points are behind 1 specific plane, we are out
    // if we are in with all points, then we are fully in
    for (int p = 0; p < 6; ++p) {
        int iInCount = 8;
        int iPtIn = 1;

        for (int i = 0; i < 8; ++i) {
            // test this point against the planes
            float distance = this->m_ViewFrustum.Planes[p]->Dot(vertices[i]);
            if (distance < 0) {
                iPtIn = 0;
                --iInCount;
            }
        }

        // were all the points outside of plane p?
        if (iInCount == 0) {
            return FRUSTUMCULLINGTYPE_NONE;
        }

        // check if they were all on the right side of the plane
        iTotalIn += iPtIn;
    }

    // so if iTotalIn is 6, then all are inside the view
    if (iTotalIn == 6) {
        return FRUSTUMCULLINGTYPE_FULL;
    }

    // we must be partly in then otherwise
    return FRUSTUMCULLINGTYPE_PARTIAL;
}

inline FrustumCullingType CPipeline::SphereInFrustum(const BoundingSphere* boundingSphere) {
    // various distances
    float fDistance;

    // calculate our distances to each of the planes
    for (int i = 0; i < 6; ++i) {
        // find the distance to this plane
        fDistance = this->m_ViewFrustum.Planes[i]->Dot(boundingSphere->center);

        // if this distance is < -sphere.radius, we are outside
        if (fDistance < -boundingSphere->radius) {
            //if (i == 1 || i == 3)
            return FRUSTUMCULLINGTYPE_NONE;
        }

        // else if the distance is between +- radius, then we intersect
        if ((float)fabs(fDistance) < boundingSphere->radius) {
            return FRUSTUMCULLINGTYPE_PARTIAL;
        }
    }

    // otherwise we are fully in view
    return FRUSTUMCULLINGTYPE_FULL;
}

#endif